package com.ning.cloud.gateway.filter;

import com.alibaba.fastjson2.JSONObject;
import com.ning.cloud.gateway.config.JwtProperties;
import com.ning.cloud.gateway.dto.AuthUserDTO;
import com.ning.cloud.gateway.enums.ErrorCode;
import com.ning.cloud.gateway.exception.ServiceException;
import com.ning.cloud.gateway.util.JwtTokenUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import com.ning.cloud.gateway.config.SecurityConfig;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * 说明：
 * ①、无需鉴权验证的接口，再无token的情况下正常通过，配置SecurityConfig#ignoreUrls上
 * ②、其他接口默认需要鉴权
 *
 */
@Slf4j
@Component
public class JwtAuthenticationFilter implements GlobalFilter, Ordered {

    /**
     * 给下游服务添加的头部用户信息
     */
    public static final String HEADER_LOGIN_USER = "x-login-user";

    /**
     * 给下游服务添加的头部token信息
     */
    public static final String HEADER_TOKEN = "x-token";

    @Resource
    SecurityConfig securityConfig;

    @Resource
    JwtProperties jwtProperties;

    @Resource
    JwtTokenUtil jwtTokenUtil;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 移除x-login-user  的请求头，避免伪造模拟
        exchange = this.removeLoginUser(exchange);

        ServerHttpRequest request = exchange.getRequest().mutate().build();
        String uri = request.getPath().toString(); //uri

        //不需要鉴权的接口
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        boolean noAuthUri = securityConfig.getIgnoreUrls().stream().anyMatch(url -> antPathMatcher.match(url, uri));
        if (noAuthUri) {
            //不需要鉴权的接口
            return this.handleNoAuthApi(exchange, chain, request);
        } else {
            //需要鉴权的接口
            return this.handleAuthApi(exchange, chain, request);
        }
    }

    /**
     * 处理需要授权的接口
     *
     * 如果用户未登录则返回错误
     *
     * @param exchange
     * @param chain
     * @param request
     * @return
     */
    private Mono<Void> handleAuthApi(ServerWebExchange exchange,
                                             GatewayFilterChain chain,
                                             ServerHttpRequest request) {
        try {
            String bearerToken = exchange.getRequest().getHeaders().getFirst(jwtProperties.getHeader());
            log.info("获取到的bearer token:{}", bearerToken);
            String jwtToken = null;
            if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(jwtProperties.getTokenPrefix())) {
                jwtToken = bearerToken.substring(jwtProperties.getTokenPrefix().length());
            }
            //解析jwt token获取用户信息
            AuthUserDTO authUserDTO = jwtTokenUtil.parseToken(jwtToken);
            if (authUserDTO == null) {
                return unauthorizedResponse(exchange, "用户未登录");
            }

            //登录用户信息加到下游请求头部
            ServerHttpRequest mutatedRequest = addUserHeaders(request, authUserDTO, jwtToken);
            return chain.filter(exchange.mutate().request(mutatedRequest).build());
        } catch (Exception e) {
            return unauthorizedResponse(exchange, "用户未登录");
        }
    }

    /**
     * 处理不需要鉴权的接口
     *
     * 如果用户未登录则跳过，用户登录则解析出登录用户信息
     *
     * @param exchange
     * @param chain
     * @param request
     * @return
     */
    private Mono<Void> handleNoAuthApi(ServerWebExchange exchange,
                                     GatewayFilterChain chain,
                                     ServerHttpRequest request) {
        try {
            String bearerToken = exchange.getRequest().getHeaders().getFirst(jwtProperties.getHeader());
            log.info("获取到的bearer token:{}", bearerToken);
            String jwtToken = null;
            if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(jwtProperties.getTokenPrefix())) {
                jwtToken = bearerToken.substring(jwtProperties.getTokenPrefix().length());
            }
            //解析jwt token获取用户信息
            AuthUserDTO authUserDTO = jwtTokenUtil.parseToken(jwtToken);
            if (authUserDTO != null) {
                ServerHttpRequest mutatedRequest = addUserHeaders(request, authUserDTO, jwtToken);
                return chain.filter(exchange.mutate().request(mutatedRequest).build());
            }

            return chain.filter(exchange);
        } catch (Exception e) {
            return chain.filter(exchange);
        }

    }

    /**
     * 设置头部信息
     * @param request
     * @param authUserDTO
     * @param token
     * @return
     */
    private ServerHttpRequest addUserHeaders(ServerHttpRequest request, AuthUserDTO authUserDTO, String token) {
        String userStr = URLEncoder.encode(JSONObject.toJSONString(authUserDTO), StandardCharsets.UTF_8);
        return request.mutate()
                .header(HEADER_LOGIN_USER, userStr)
                .header(HEADER_TOKEN, token)
                .build();
    }

    /**
     * 未授权输出
     * @param exchange
     * @param message
     * @return
     */
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

        String body = "{\"code\": 401, \"message\": \"" + message + "\"}";
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));

        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 删除头部登录用户信息
     * @param exchange
     * @return
     */
    public static ServerWebExchange removeLoginUser(ServerWebExchange exchange) {
        // 如果不包含，直接返回
        if (!exchange.getRequest().getHeaders().containsKey(HEADER_LOGIN_USER)) {
            return exchange;
        }
        // 如果包含，则移除。参考 RemoveRequestHeaderGatewayFilterFactory 实现
        ServerHttpRequest request = exchange.getRequest().mutate()
                .headers(httpHeaders -> httpHeaders.remove(HEADER_LOGIN_USER)).build();
        return exchange.mutate().request(request).build();
    }

    @Override
    public int getOrder() {
        return -1; //
    }
}
